package org.jsets.fastboot.oss.service.impl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.jsets.fastboot.cache.ICache;
import org.jsets.fastboot.oss.config.ObjectStorageProperties;
import org.jsets.fastboot.oss.model.dto.MultipartResult;
import org.jsets.fastboot.oss.model.entity.FileMeta;
import org.jsets.fastboot.oss.model.entity.Storage;
import org.jsets.fastboot.oss.service.IMinioService;
import org.jsets.fastboot.oss.service.IStorageEngineService;
import org.jsets.fastboot.oss.util.CustomMinioClient;
import org.jsets.fastboot.util.IoUtils;
import org.jsets.fastboot.util.StringUtils;
import org.springframework.cache.CacheManager;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 * MINIO 文件系统
 * 
 * @author wangjie
 *
 */
@Slf4j
public class MinioStorageService implements IMinioService {

	private static final String CACHE_NAME = "multipart";
	private static final String CHUNK_UPLOAD_PREFIX = "/multipart/chunk_upload";
	private final ObjectStorageProperties properties;
	private final ICache cache;
	private final Storage config;
	private CustomMinioClient client;
	
	public MinioStorageService(ICache cache, ObjectStorageProperties properties, Storage config) {
		this.properties = properties;
		this.config = config;
		this.cache = cache;
	}
	
	@Override
	public void initialize() {
		log.info("创建Minio客户端[{}]", this.config.getAccessUrl());
		try {
			this.client = new CustomMinioClient(MinioClient.builder()
					.endpoint(this.config.getAccessUrl())
					.credentials(this.config.getAccessKey(), this.config.getSecretKey())
					.build());
			//like ping?
			List<Bucket> ls = this.client.listBuckets();
			ls = null;
		} catch (Exception e) {
			throw new RuntimeException("创建Minio客户端失败： "+e.getMessage(), e);
		}
	}

	@Override
	public void makeBucket(String bucket) {
		BucketExistsArgs existsArgs = BucketExistsArgs.builder().bucket(bucket).build();
		MakeBucketArgs makeArgs = MakeBucketArgs.builder().bucket(bucket).build();
		try {
			boolean exist = this.client.bucketExists(existsArgs);
			if (!exist) {
				this.client.makeBucket(makeArgs);
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
	}
	
	@Override
	public void removeBucket(String bucket) {
		RemoveBucketArgs removeBucketArgs = RemoveBucketArgs.builder().bucket(bucket).build();
		try {
			this.client.removeBucket(removeBucketArgs);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	@Override
	public List<String> listBuckets() {
		List<String> result = Lists.newArrayList();
		try {
			List<Bucket> list = this.client.listBuckets();
			list.forEach(t -> {
				result.add(t.name());
			});
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
		return result;
	}

	@Override
	public void upload(FileMeta metadata, InputStream inputStream) {
		String bucket = metadata.getBucketName();
		String id = Long.toString(metadata.getId());
		try {
			PutObjectArgs.Builder builder = PutObjectArgs
					.builder()
					.bucket(bucket)
					.object(id)
					.stream(inputStream, inputStream.available(), -1);
			if(StringUtils.notEmpty(metadata.getContentType())) {
				builder.contentType(metadata.getContentType());
			}
			this.client.putObject(builder.build());
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			IoUtils.closeQuietly(inputStream);
		}
	}

	@Override
	public void upload(FileMeta metadata, byte[] data) {
		String bucket = metadata.getBucketName();
		String id = Long.toString(metadata.getId());
		InputStream stream = null;
		try {
			stream = new ByteArrayInputStream(data);
			PutObjectArgs uploadObject = PutObjectArgs
					.builder()
					.bucket(bucket)
					.object(id)
					.stream(stream, stream.available(), -1)
					.build();
			this.client.putObject(uploadObject);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			IoUtils.closeQuietly(stream);
		}
	}

	@Override
	public void remove(FileMeta metadata) {
		String bucket = metadata.getBucketName();
		String id = Long.toString(metadata.getId());
		try {
			this.client.removeObject(
					RemoveObjectArgs.builder()
					.bucket(bucket)
					.object(id)
					.build());
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	@Override
	public InputStream download(FileMeta metadata) {
		String bucket = metadata.getBucketName();
		String id = Long.toString(metadata.getId());
		try {
			return this.client.getObject(
					GetObjectArgs.builder()
					.bucket(bucket)
					.object(id)
					.build());
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
	}
	
	public MultipartResult initMultiPartUpload(String bucketName, String objectName, String filename, Long size) {
		MultipartResult result = new MultipartResult();
		long partCount = size % this.properties.getPartSize() != 0 ? size / this.properties.getPartSize() + 1 : size / this.properties.getPartSize();
        String contentType = "application/octet-stream";
        HashMultimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", contentType);
        try {
	        String uploadId = this.client.initMultiPartUpload(bucketName, null, objectName, headers, null);
	        List<String> uploadUrlList = Lists.newArrayList();
	        Map<String, String> queryParams = Maps.newHashMap();
	        queryParams.put("uploadId", uploadId);
	        for (int i = 1; i <= partCount; i++) {
	        	queryParams.put("partNumber", String.valueOf(i));
	            String uploadUrl = this.client.getPresignedObjectUrl(
	                    GetPresignedObjectUrlArgs.builder()
	                            .method(Method.PUT)
	                            .bucket(bucketName)
	                            .object(objectName)
	                            .expiry(1, TimeUnit.DAYS)
	                            .extraQueryParams(queryParams)
	                            .build());
	            String chunk = "chunk_" + (i - 1);
	            uploadUrlList.add(CHUNK_UPLOAD_PREFIX + "?uploadId=" + uploadId + "&chunk=" + chunk);
	            String chunkKey = uploadId+"_"+chunk;
	            this.cache.put(chunkKey, uploadUrl, 86400);//on day
	        }
            result.setPartCount(partCount);
            result.setUploadId(uploadId);
            result.setUploadUrlList(uploadUrlList);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
        return result;
    }
	
//    public String chunkUpload(String uploadId, InputStream inputStream, String chunk) {
//    	this.client.par
//    }

	

	@Override
	public void close() {
	}

}